﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace DBS_semka_2
{
    [Serializable]
    class cHashFile
    {
        private int piKeyLength;

        private int piRecordLength;

        private int piHeaderLength;

        private int piNumberOfRecordsInBucket;

        private cFileManipulation poFile;

        private int piUsedBits;

        private ArrayList pItems = new ArrayList();

        private int piNumberOfDeleted;

        // Pomer, po dosiahnuti ktoreho sa ma reorganizovat pole smernikov
        // Pomer pocet vymazanych / velkost bucketu
        
        // Pre vyhladavanie dalsieho kluca
        private int piDeleteReorganizeRatio = 1;

        private byte[] paLastKey = null;

        private int piFoundCount = 0;

        private bool pbUseImmediateStats = false;

        public bool bUseImmediateStats
        {
            get { return pbUseImmediateStats; }
            set { pbUseImmediateStats = value; }
        }

        private long piLastAddressForSearch;
        
        public long iNumberOfBlocks
        {
            get { return poFile.iNumberOfBlocks; }
        }

        public int iDeleteReorganizeRatio
        {
            get { return piDeleteReorganizeRatio; }
            set { piDeleteReorganizeRatio = value; }
        }

        public cHashFile(string pa_sFileName, int pa_iKeyLength, int pa_iNumberOfRecordsInBucket)
        {
            piUsedBits = 1;
            piKeyLength = pa_iKeyLength;
            piNumberOfRecordsInBucket = pa_iNumberOfRecordsInBucket;
            piRecordLength = sizeof(long) * 2 + pa_iKeyLength;
            poFile = new cFileManipulation(pa_sFileName, pa_iNumberOfRecordsInBucket, piRecordLength, piKeyLength);
            piHeaderLength = poFile.iLengthOfHeader;
            pItems.Add(poFile.createNewBlock());
            pItems.Add(poFile.createNewBlock());
        }

        /// <summary>
        /// Porovna 2 polia bytov, ci sa rovnaju
        /// </summary>
        /// <param name="pa_aArr1"></param>
        /// <param name="pa_aArr2"></param>
        /// <returns></returns>
        private bool compareByte(byte[] pa_aArr1, byte[] pa_aArr2)
        {
            bool bReturn = true;
            for (int ii = 0; ii < pa_aArr1.Length; ii++)
            {
                if (pa_aArr1[ii] != pa_aArr2[ii])
                {
                    bReturn = false;
                }
            }
            return bReturn;
        }

         // Reorganizuje pole
        private void reorganizeArray(int pa_iNewDepth)
        {
            if (pa_iNewDepth > piUsedBits)
            {
                int iCount = pItems.Count;
                for (int ii = 0; ii < iCount; ii++)
                {
                    pItems.Add(null);
                }
                for (int ii = iCount- 1; ii >= 0; ii--)
                {
                        pItems[ii * 2] = pItems[ii];
                        pItems[ii * 2 + 1] = pItems[ii];
                }
            }
            else
            {
                for (int ii = 0; ii < pItems.Count / 2; ii++)
                {
                    pItems[ii] = pItems[ii * 2];
                }
                pItems.RemoveRange(pItems.Count / 2, pItems.Count / 2);
            }
            piUsedBits = pa_iNewDepth;
        }

        /// <summary>
        /// Vlozi hodnotu
        /// </summary>
        /// <param name="pa_oKeys"></param>
        /// <param name="pa_oValues"></param>
        public void insertValue(byte[] pa_aKey, long pa_iAddress, long pa_iRowid)
        {
            uint hash = getHash(pa_aKey);
            string binar = convertToBinary(hash);
            int iArrayPosition = convertFromBinary(binar.Substring(0,piUsedBits));
            long oldAdresa = (long)pItems[iArrayPosition];
            cBlock oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, oldAdresa, poFile.readBlock(oldAdresa));
            
            if (oTempBlock.iNumberOfRecords == piNumberOfRecordsInBucket)
            {
                if (piUsedBits < 32)
                {
                    List<cBlock> aBlocks = new List<cBlock>();
                    aBlocks.Add(oTempBlock);
                    if (oTempBlock.iChain != -1)
                    {
                        long iChain = oTempBlock.iChain;
                        while (iChain != -1)
                        {
                            oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, oldAdresa, poFile.readBlock(iChain));
                            iChain = oTempBlock.iChain;
                            aBlocks.Add(oTempBlock);
                        }
                    }
                    // Spocita kolko hodnot hash funkcie je v danom bloku
                    Dictionary<string, int> aKeyHashValues = new Dictionary<string, int>();
                    for (int ii = 0; ii < aBlocks.Count; ii++)
                    {
                        for (int jj = 0; jj < aBlocks[ii].iNumberOfRecords; jj++)
                        {
                            string sHashValue = convertToBinary(getHash(aBlocks[ii][jj].aKey));
                            if (aKeyHashValues.ContainsKey(sHashValue) == true)
                            {
                                aKeyHashValues[sHashValue] = aKeyHashValues[sHashValue] + 1;
                            }
                            else
                            {
                                aKeyHashValues.Add(sHashValue, 1);
                            }
                        }
                    }

                    string sHashVal = convertToBinary(getHash(pa_aKey));
                    if (aKeyHashValues.ContainsKey(sHashVal) == true)
                    {
                        aKeyHashValues[sHashVal] = aKeyHashValues[sHashVal] + 1;
                    }
                    else
                    {
                        aKeyHashValues.Add(sHashVal, 1);
                    }

                    if (aKeyHashValues.Count == 1)
                    {
                        //Bucket obsahuje hodnoty len s 1 hash funkciou a treba pridat preplnovaci blok alebo doplnit existujuci

                        if (aBlocks[aBlocks.Count - 1].iNumberOfRecords < piNumberOfRecordsInBucket)
                        {
                            aBlocks[aBlocks.Count - 1].addRecord(pa_aKey, pa_iRowid, pa_iAddress);
                            poFile.writeBlock(aBlocks[aBlocks.Count - 1].iAddress, aBlocks[aBlocks.Count - 1].toByteArray());
                        }
                        else
                        {
                            long iNewAddress = poFile.createNewBlock();
                            cBlock oNewBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, iNewAddress);
                            oNewBlock.addRecord(pa_aKey, pa_iRowid, pa_iAddress);
                            poFile.writeBlock(oNewBlock.iAddress, oNewBlock.toByteArray());
                            aBlocks[aBlocks.Count - 1].iChain = oNewBlock.iAddress;
                            poFile.writeBlock(aBlocks[aBlocks.Count - 1].iAddress, aBlocks[aBlocks.Count - 1].toByteArray());
                        }
                    }
                    else
                    {
                        int iDepth = aBlocks[0].iDepth;
                        //Netreba reorganizovat pole
                        if (iDepth < piUsedBits)
                        {
                            string sBinar = convertToBinary(getHash(pa_aKey));
                            int iFirstPosition = (int)convertFromBinary(((sBinar).Substring(0, iDepth)).PadRight(piUsedBits, '0'));
                            if (iFirstPosition == 1)
                            {
                                iFirstPosition = 0;
                            }
                            int iNumberOfSlots = (int)Math.Pow(2, piUsedBits - iDepth);
                            int iUpperHalf = iFirstPosition + iNumberOfSlots / 2;

                            string sBin = convertToBinaryNo(iUpperHalf);
                            sBin = sBin.PadLeft(piUsedBits, '0');
                            if (sBin.Length < iDepth + 1)
                            {
                                sBin = sBin.PadLeft(iDepth + 1, '0');
                            }
                            int iUpperNo = convertFromBinary((sBin.Substring(0, iDepth + 1)));
                            //Console.WriteLine("sBin :" + sBin + " // " + Convert.ToString(iUpperNo));
                            List<cRecord> oFirstHalf = new List<cRecord>();
                            List<cRecord> oSecondHalf = new List<cRecord>();
                            // Rozdelenie na 2 casti podla hodnoty hash funkcie
                            for (int ii = 0; ii < aBlocks.Count; ii++)
                            {
                                for (int jj = 0; jj < aBlocks[ii].iNumberOfRecords; jj++)
                                {
                                    string sBinRepr = convertToBinary(getHash(aBlocks[ii][jj].aKey));
                                    if (string.Compare(sBinRepr.Substring(0, iDepth + 1), sBin.Substring(0, iDepth + 1)) >= 0)
                                    {
                                        oSecondHalf.Add(aBlocks[ii][jj]);
                                    }
                                    else
                                    {
                                        oFirstHalf.Add(aBlocks[ii][jj]);
                                    }
                                }
                            }

                            // Vlozenie prvej casti
                            long iPosition = oldAdresa;
                            // Ktory index z pola zretazenych blokov sa pouziva
                            int iUsedIndex = 0;

                            if (oFirstHalf.Count > piNumberOfRecordsInBucket)
                            {
                                while (oFirstHalf.Count != 0)
                                {
                                    int iRecordsToMoveCount = 0;
                                    if (oFirstHalf.Count < piNumberOfRecordsInBucket)
                                    {
                                        iRecordsToMoveCount = oFirstHalf.Count;
                                    }
                                    else
                                    {
                                        iRecordsToMoveCount = piNumberOfRecordsInBucket;
                                    }

                                    oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, aBlocks[iUsedIndex].iAddress);
                                    for (int ii = oFirstHalf.Count - iRecordsToMoveCount; ii < oFirstHalf.Count; ii++)
                                    {
                                        oTempBlock.addRecord(oFirstHalf[ii].aKey, oFirstHalf[ii].iRowid, oFirstHalf[ii].iAddress);
                                    }

                                    oFirstHalf.RemoveRange(oFirstHalf.Count - iRecordsToMoveCount, iRecordsToMoveCount);
                                    if (oFirstHalf.Count != 0)
                                    {
                                        oTempBlock.iChain = aBlocks[iUsedIndex + 1].iAddress;
                                    }
                                    oTempBlock.iDepth = iDepth + 1;

                                    poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
                                    iUsedIndex++;

                                }
                            }
                            else
                            {
                                oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, aBlocks[iUsedIndex].iAddress);
                                for (int ii = 0; ii < oFirstHalf.Count; ii++)
                                {
                                    oTempBlock.addRecord(oFirstHalf[ii].aKey, oFirstHalf[ii].iRowid, oFirstHalf[ii].iAddress);
                                }
                                oTempBlock.iDepth = iDepth + 1;

                                poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
                                iUsedIndex++;
                            }
                            // Vlozenie druhej polovice
                            long iNewAddress = -1;
                            if (oSecondHalf.Count > piNumberOfRecordsInBucket)
                            {
                                long iChainAddres = -1;
                                while (oSecondHalf.Count != 0)
                                {
                                    long iLastAddress;
                                    int iRecordsToMoveCount = 0;
                                    if (oSecondHalf.Count < piNumberOfRecordsInBucket)
                                    {
                                        iRecordsToMoveCount = oSecondHalf.Count;
                                    }
                                    else
                                    {
                                        iRecordsToMoveCount = piNumberOfRecordsInBucket;
                                    }

                                    if (iUsedIndex < aBlocks.Count)
                                    {
                                        iLastAddress = aBlocks[iUsedIndex].iAddress;
                                    }
                                    else
                                    {
                                        if (iChainAddres != -1)
                                        {
                                            iLastAddress = iChainAddres;
                                        }
                                        else
                                        {
                                            iLastAddress = poFile.createNewBlock();
                                        }
                                    }
                                    if (iNewAddress == -1)
                                    {
                                        iNewAddress = iLastAddress;
                                    }
                                    oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, iLastAddress);
                                    for (int ii = oSecondHalf.Count - iRecordsToMoveCount; ii < oSecondHalf.Count; ii++)
                                    {
                                        oTempBlock.addRecord(oSecondHalf[ii].aKey, oSecondHalf[ii].iRowid, oSecondHalf[ii].iAddress);
                                    }

                                    oSecondHalf.RemoveRange(oSecondHalf.Count - iRecordsToMoveCount, iRecordsToMoveCount);
                                    if (oSecondHalf.Count != 0)
                                    {
                                        if (iUsedIndex + 1 < aBlocks.Count)
                                        {
                                            oTempBlock.iChain = aBlocks[iUsedIndex + 1].iAddress;
                                        }
                                        else
                                        {
                                            iChainAddres = poFile.createNewBlock();
                                            oTempBlock.iChain = iChainAddres;
                                        }
                                    }
                                    oTempBlock.iDepth = iDepth + 1;

                                    poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
                                    iUsedIndex++;
                                }
                            }
                            else
                            {
                                if (iUsedIndex < aBlocks.Count)
                                {
                                    oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, aBlocks[iUsedIndex].iAddress);
                                }
                                else
                                {
                                    long iAddress = poFile.createNewBlock();
                                    oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, iAddress);
                                }
                                if (iNewAddress == -1)
                                {
                                    iNewAddress = oTempBlock.iAddress;
                                }
                                for (int ii = 0; ii < oSecondHalf.Count; ii++)
                                {
                                    oTempBlock.addRecord(oSecondHalf[ii].aKey, oSecondHalf[ii].iRowid, oSecondHalf[ii].iAddress);
                                }
                                oTempBlock.iDepth = iDepth + 1;

                                poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
                                iUsedIndex++;
                            }

                            // Prepisanie pola s adresami
                            for (int ii = iUpperHalf; ii <= iUpperHalf + (iNumberOfSlots / 2) - 1; ii++)
                            {
                                pItems[ii] = iNewAddress;
                            }

                            //Console.WriteLine(Convert.ToString(iUpperHalf) + "-" + "polovica" + " - " + Convert.ToString(oldAdresa));
                            if (iUsedIndex < aBlocks.Count)
                            {
                                for (int ii = iUsedIndex; ii < aBlocks.Count; ii++)
                                {
                                    poFile.cleanBlock(aBlocks[ii].iAddress);
                                }
                            }

                            insertValue(pa_aKey, pa_iAddress, pa_iRowid);
                        }
                        else
                        {
                            //Treba reorganizovat pole
                            reorganizeArray(iDepth + 1);
                            //piUsedBits++;
                            insertValue(pa_aKey, pa_iAddress, pa_iRowid);
                        }
                    }
                }

                else
                {
                    if (piUsedBits == 32)
                    {
                        //Neda sa viac rozdelit pole, treba vytvorit preplnovaci bucket
                        long iPosition = (long)pItems[iArrayPosition];
                        oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, iPosition, poFile.readBlock(iPosition));
                        long iChain = oTempBlock.iChain;
                        while (iChain != -1)
                        {
                            oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, iChain, poFile.readBlock(iChain));
                            iChain = oTempBlock.iChain;
                        }
                        if (oTempBlock.iChain < piNumberOfRecordsInBucket)
                        {
                            oTempBlock.addRecord(pa_aKey, pa_iRowid, pa_iAddress);
                            poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
                        }
                        else
                        {
                            long iNewAddress = poFile.createNewBlock();
                            oTempBlock.iChain = iNewAddress;
                            poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());

                            oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, iNewAddress, poFile.readBlock(iNewAddress));
                            oTempBlock.addRecord(pa_aKey, pa_iRowid, pa_iAddress);
                            poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
                        }
                    }
                }
            }
            else
            {
                ArrayList oValues = new ArrayList();
                oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, (long)pItems[iArrayPosition], poFile.readBlock((long)pItems[iArrayPosition]));
                oTempBlock.addRecord(pa_aKey, pa_iRowid, pa_iAddress);
                poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
            }
        }

        
        /// <summary>
        /// Updatuje hodnotu s klucami
        /// </summary>
        /// <param name="pa_oKeys"></param>
        /// <param name="pa_oValues"></param>
        /// <returns></returns>
        public bool updateValue(byte[] pa_aKey, byte[] pa_aNewKey, long pa_iAddress, long pa_iRowid)
        {
            if (compareByte(pa_aKey, pa_aNewKey) == false)
            {
                deleteValue(pa_aKey, pa_iRowid);
                insertValue(pa_aKey, pa_iAddress, pa_iRowid);
                return true;
            }
            else
            {
                uint hash = getHash(pa_aKey);
                string binar = convertToBinary(hash);
                int iArrayPosition = convertFromBinary(binar.Substring(0, piUsedBits));
                int iFound;
                cBlock oTempBlock = new  cBlock(piKeyLength, piNumberOfRecordsInBucket, (long)pItems[iArrayPosition], poFile.readBlock((long)pItems[iArrayPosition]));
                iFound = oTempBlock.containsKey(pa_aKey, pa_iRowid);
                while(iFound == -1 && oTempBlock.iChain != -1)
                {
                    oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, oTempBlock.iChain, poFile.readBlock(oTempBlock.iChain));
                    iFound = oTempBlock.containsKey(pa_aKey, pa_iRowid);
                }
                if (iFound == -1)
                {
                    return false;
                }
                else
                {
                    oTempBlock[iFound].iAddress = pa_iAddress;
                    poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
                    return true;
                }
            }
        }
        


        /// <summary>
        /// Vymaze zaznam s danym klucom a rowid
        /// </summary>
        /// <param name="pa_aKey"></param>
        /// <param name="pa_iRowid"></param>
        public bool deleteValue(byte[] pa_aKey, long pa_iRowid)
        {
            uint hash = getHash(pa_aKey);
            string binar = convertToBinary(hash);
            int iArrayPosition = convertFromBinary(binar.Substring(0, piUsedBits));
            int iFound;
            List<cBlock> aBlocks = new List<cBlock>();
            cBlock oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, (long)pItems[iArrayPosition], poFile.readBlock((long)pItems[iArrayPosition]));
            aBlocks.Add(oTempBlock);
            iFound = oTempBlock.containsKey(pa_aKey, pa_iRowid);
            while (iFound == -1 && oTempBlock.iChain != -1)
            {
                oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, oTempBlock.iChain, poFile.readBlock(oTempBlock.iChain));
                iFound = oTempBlock.containsKey(pa_aKey, pa_iRowid);
                aBlocks.Add(oTempBlock);
            }
            if (iFound == -1)
            {
                return false;
            }
            else
            {
                if (oTempBlock.iNumberOfRecords > 1)
                {
                    oTempBlock[iFound] = oTempBlock[oTempBlock.iNumberOfRecords - 1];
                    oTempBlock.deleteRecord(oTempBlock.iNumberOfRecords - 1);
                    oTempBlock.iNumberOfRecords--;
                    if (oTempBlock.iChain != -1)
                    {
                        int iOrderNo = aBlocks.Count - 1;
                        oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, oTempBlock.iChain, poFile.readBlock(oTempBlock.iChain));
                        aBlocks.Add(oTempBlock);
                        while (oTempBlock.iChain != -1)
                        {
                            oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, oTempBlock.iChain, poFile.readBlock(oTempBlock.iChain));
                            aBlocks.Add(oTempBlock);
                        }
                        aBlocks[iOrderNo].addRecord(oTempBlock[oTempBlock.iNumberOfRecords - 1].aKey, oTempBlock[oTempBlock.iNumberOfRecords - 1].iRowid, oTempBlock[oTempBlock.iNumberOfRecords - 1].iAddress);
                        oTempBlock.deleteRecord(oTempBlock.iNumberOfRecords - 1);
                        oTempBlock.iNumberOfRecords--;
                        if (oTempBlock.iNumberOfRecords == 0)
                        {
                            aBlocks[aBlocks.Count - 2].iChain = -1;
                            poFile.cleanBlock(oTempBlock.iAddress);
                            poFile.writeBlock(aBlocks[aBlocks.Count - 2].iAddress, aBlocks[aBlocks.Count - 2].toByteArray());
                            if (iOrderNo != aBlocks.Count - 2)
                            {
                                poFile.writeBlock(aBlocks[iOrderNo].iAddress, aBlocks[iOrderNo].toByteArray());
                            }
                        }
                        else
                        {
                            poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
                            poFile.writeBlock(aBlocks[iOrderNo].iAddress, aBlocks[iOrderNo].toByteArray());
                        }
                    }
                    else
                    {
                        poFile.writeBlock(oTempBlock.iAddress, oTempBlock.toByteArray());
                    }
                }
                else
                {
                    poFile.cleanBlock(oTempBlock.iAddress);
                    if (aBlocks.Count > 1)
                    {
                        aBlocks[aBlocks.Count - 2].iChain = -1;
                        poFile.writeBlock(aBlocks[aBlocks.Count - 2].iAddress, aBlocks[aBlocks.Count - 2].toByteArray());
                    }
                }
                piNumberOfDeleted++;
                if ( Convert.ToInt32(piNumberOfDeleted / piNumberOfRecordsInBucket) >= piDeleteReorganizeRatio)
                {
                    optimizeArray();
                }
                return true;
            } 
        }

        /// <summary>
        /// Najde kluc
        /// </summary>
        /// <param name="pa_aKey"></param>
        /// <returns></returns>
        public long findKey(byte[] pa_aKey)
        {
            uint hash = getHash(pa_aKey);
            string binar = convertToBinary(hash);
            int iArrayPosition = convertFromBinary(binar.Substring(0, piUsedBits));
            int iFound;
            cBlock oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, (long)pItems[iArrayPosition], poFile.readBlock((long)pItems[iArrayPosition]));
            iFound = oTempBlock.containsKey(pa_aKey, 1);
            while (iFound == -1 && oTempBlock.iChain != -1)
            {
                oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, oTempBlock.iChain, poFile.readBlock(oTempBlock.iChain));
                iFound = oTempBlock.containsKey(pa_aKey, 1);
            }
            if (iFound == -1)
            {
                return -1;
            }
            else
            {
                paLastKey = pa_aKey;
                piFoundCount = 1;
                piLastAddressForSearch = oTempBlock.iAddress;
                return oTempBlock[iFound].iAddress;
            }
        }

        /// <summary>
        /// Najde dalsi kluc
        /// </summary>
        /// <param name="pa_aKey"></param>
        /// <returns></returns>
        public long findNextKey(byte[] pa_aKey)
        {
            if (compareByte(pa_aKey, paLastKey) == true)
            {
                int iFound;
                cBlock oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, piLastAddressForSearch, poFile.readBlock(piLastAddressForSearch));
                iFound = oTempBlock.containsKey(pa_aKey, piFoundCount + 1);
                while (iFound == -1 && oTempBlock.iChain != -1)
                {
                    oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, oTempBlock.iChain, poFile.readBlock(oTempBlock.iChain));
                    piFoundCount = 0;
                    iFound = oTempBlock.containsKey(pa_aKey, piFoundCount + 1);
                }
                if (iFound == -1)
                {
                    return -1;
                }
                else
                {
                    piFoundCount++;
                    piLastAddressForSearch = oTempBlock.iAddress;
                    return (oTempBlock[iFound]).iAddress;
                }
            }
            else
            {
                uint hash = getHash(pa_aKey);
                string binar = convertToBinary(hash);
                int iArrayPosition = convertFromBinary(binar.Substring(0, piUsedBits));
                int iFound;
                cBlock oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, (long)pItems[iArrayPosition], poFile.readBlock((long)pItems[iArrayPosition]));
                iFound = oTempBlock.containsKey(pa_aKey, 1);
                while (iFound == -1 && oTempBlock.iChain != -1)
                {
                    oTempBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, oTempBlock.iChain, poFile.readBlock(oTempBlock.iChain));
                    iFound = oTempBlock.containsKey(pa_aKey, 1);
                }
                if (iFound == -1)
                {
                    return -1;
                }
                else
                {
                    paLastKey = pa_aKey;
                    piFoundCount = 1;
                    piLastAddressForSearch = oTempBlock.iAddress;
                    return oTempBlock[iFound].iAddress;
                }
            }
        }

        

        /// <summary>
        /// Vrati hash hodnotu pre kluce
        /// </summary>
        /// <param name="pa_oKeys"></param>
        /// <returns></returns>
        public uint getHash(byte[] pa_oKeys)
        {
            //int iHash = ((ByteArrayToObject(pa_oKeys[0])).GetHashCode());
            //int ire = iHash + iHash % 37491;
            //return ire;
            return fnvHash(pa_oKeys);
        }

        private uint bernstainHash(byte[] pa_oKey)
        {
            byte[] oHlp = new byte[pa_oKey.Length];
            pa_oKey.CopyTo(oHlp, 0);
            uint h = 0;

            for (int i = 0; i < pa_oKey.Length; i++)
            {
                h = 33 * h + oHlp[i];
            }

            return h;
        }

        private uint modifiedBernstainHash(byte[] pa_oKey)
        {
            byte[] oHlp = new byte[pa_oKey.Length];
            pa_oKey.CopyTo(oHlp, 0);
            uint h = 0;

            for (int i = 0; i < pa_oKey.Length; i++)
            {
                h ^= (h << 5) + (h >> 2) + oHlp[i];
            }

            return h;
        }

        private uint fnvHash(byte[] pa_oKey)
        {
            byte[] oHlp = new byte[pa_oKey.Length];
            pa_oKey.CopyTo(oHlp, 0);
            uint h = 2166136261;

            for (int i = 0; i < pa_oKey.Length; i++)
            {

                h = (h * 16777619) ^ oHlp[i];

            }

            return h;
        }

        private uint myHash(byte[] pa_oKey)
        {
            uint iReturn = 0;
            for (int ii = 0; ii < pa_oKey.Length; ii++)
            {
                if (ii % 2 == 0)
                {
                    iReturn += (uint)pa_oKey[ii] * 7;
                }
                else
                {
                    iReturn += (uint)pa_oKey[ii] * 5;
                }
                iReturn = iReturn >> 2;
            }
            return iReturn;
        }

        private string Reverse(string pa_sString)
        {
            string sReturn = "";
            
            for (int i = pa_sString.Length -1 ; i > -1; i--)
            {
                sReturn += pa_sString[i];
            }
            return sReturn;
            
        }
        /// <summary>
        /// Konverzia do binarnej hodnoty
        /// </summary>
        /// <param name="pa_iNumber"></param>
        /// <returns></returns>
         private string convertToBinary(uint pa_iNumber)
        {
            string sReturn = "";
            for (; pa_iNumber > 0; pa_iNumber/=2 )
            {
                sReturn = Convert.ToString(pa_iNumber % 2) + sReturn;
            }
            sReturn = sReturn.PadRight(32, '0');
            //return sReturn;
            return Reverse(sReturn);
            
        }

        /// <summary>
        /// Konverzia z binarnej podoby
        /// </summary>
        /// <param name="pa_sNumber"></param>
        /// <returns></returns>
        private int convertFromBinary(string pa_sNumber)
        {
            int iReturn = 0;
            int iCount = 0;
            for (int i = pa_sNumber.Length - 1; i >= 0; i--)
			{
                if (pa_sNumber[i] != '0')
                {
                    iReturn += (int)Math.Pow(2, iCount);
                }
                iCount++;
			}
            return iReturn;
        }

        

        private string convertToBinaryNo(int pa_iNumber)
        {
            string sReturn = "";
            if (pa_iNumber < 0)
            {
                pa_iNumber *= -1;
            }
            for (; pa_iNumber > 0; pa_iNumber/=2 )
            {
                sReturn = Convert.ToString(pa_iNumber % 2) + sReturn;
            }
            //sReturn = sReturn.PadLeft(32, '0');
            //return sReturn;
            return sReturn;
        }

        /// <summary>
        /// Uprava pola smernikov pri vymazani dostatocneho poctu zaznamov
        /// </summary>
        private void optimizeArray()
        {
            bool wasChange = true;
            piNumberOfDeleted = 0;
            while(wasChange == true)
            {
                wasChange = false;
                int iPostion = 0;
                cBlock oFirstBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, (long)pItems[iPostion], poFile.readBlock((long)pItems[iPostion]));
                int iStartDepth = oFirstBlock.iDepth;
                bool bSame = true;
                while(iPostion <= pItems.Count -1)
                {
                    oFirstBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, (long)pItems[iPostion], poFile.readBlock((long)pItems[iPostion]));
                    int iActDepth = oFirstBlock.iDepth;
                    if(iActDepth != iStartDepth)
                    {
                       bSame = false;
                    }
                    int iJump = (int)Math.Pow(2, piUsedBits - iActDepth);
                    if(iActDepth != 1)
                    {
                        int iActRecNo = oFirstBlock.iNumberOfRecords;
                        // je este kde posuvat
                        if(iPostion + iJump <= pItems.Count -1)
                        {
                            cBlock oSecondBlock = new cBlock(piKeyLength, piNumberOfRecordsInBucket, (long)pItems[iPostion+ iJump], poFile.readBlock((long)pItems[iPostion + iJump]));
                            // hlbky sa rovnaju
                            if (iActDepth == oSecondBlock.iDepth)
                            {
                                string sBin = convertToBinaryNo(iPostion);
                                sBin = sBin.PadLeft(piUsedBits, '0');
                                string sJumpBin = convertToBinaryNo(iPostion + iJump);
                                sJumpBin = sJumpBin.PadLeft(piUsedBits, '0');
                                // je rovnaky aj d-ty bit sprava
                                if(sJumpBin.Substring(0,iActDepth - 1) == sBin.Substring(0,iActDepth -1))
                                {
                                    // zmestia sa do jedneho
                                    if(iActRecNo + oSecondBlock.iNumberOfRecords <= piNumberOfRecordsInBucket)
                                    {
                                        for (int ii = 0; ii < oSecondBlock.iNumberOfRecords; ii++)
                                        {
                                            oFirstBlock.addRecord(oSecondBlock[ii].aKey, oSecondBlock[ii].iRowid, oSecondBlock[ii].iAddress);
                                        }
                                        oFirstBlock.iDepth--;
                                        poFile.writeBlock(oFirstBlock.iAddress, oFirstBlock.toByteArray());
                                        poFile.cleanBlock(oSecondBlock.iAddress);
                                        for (int ii = iPostion + iJump; ii < iPostion + iJump*2; ii++)
                                        {
                                            pItems[ii] = pItems[iPostion];
                                        }
                                        iJump *=2;
                                        wasChange = true;
                                    }
                                }
                            }
                        }
                    }
                    iPostion = iPostion + iJump;
                }
                // vsetky 
                if (bSame == true && pItems.Count != 2 && iStartDepth < piUsedBits)
                {
                    reorganizeArray(piUsedBits - 1);
                }
            }
        }
    }
}
